﻿using System;
using System.Collections.Generic;
using System.Text;
using Un4seen.Bass;
using Un4seen.Bass.AddOn.Mix;
using System.IO;
using mxplay.Properties;
using System.Windows.Forms;
using System.ComponentModel;

namespace mxplay
{
	public class BassCore : IDisposable
	{
		private const int mixer_channels=2;

		#region ctor
		public BassCore()
		{
			//по умолчаниию
			//64-битные библитеки должны быть в bass.x64
			//а 32 битные в bass.x86

			//херь какая-то, из Settings.Default.BassDllPath читается мусор,
			//хотя должна быть по идее пустая строка
			//на самом деле tool в Settings.settings записывает <value Profile blabla /> вместо <value Profile blabla></value>
			//когда это должна быть пустая строка
			//при этом в ...exe.config при сборке валится такая херь: \n\r\t\t\t\t
			//баг в SharpDevelop
			//Обходим так:
			//В Settings записываем дурацкую строку и надеемся, что либы в такую директорию никто никогда не запишет
			//Если юзер Settings не сохраняет, то всё хорошо
			//А если таки сохраняет, то пустая строка записывается в user settings приемлемо
			//обход:
			if(Settings.Default.BassDllPath=="Yes, Im empty") Settings.Default.BassDllPath=string.Empty;
			
			//из этой директории будут загружаться библ. bass
			string dll_path=string.IsNullOrEmpty(Settings.Default.BassDllPath) ?
				Path.Combine(Application.StartupPath, IntPtr.Size == 8 ? "bass.x64" : "bass.x86") :
				Settings.Default.BassDllPath;
//			string dll_path=Path.Combine(Application.StartupPath, IntPtr.Size == 8 ? "bass.x64" : "bass.x86");

			#if DEBUG
			System.Globalization.CultureInfo ci=Application.CurrentCulture;
			Program.LogMessage(string.Format("Current culture: {0}",ci));
			Program.LogMessage(string.Format("Current thread UI culture: {0}",System.Threading.Thread.CurrentThread.CurrentUICulture));
			Program.LogMessage(string.Format("Localization string 'tag_albumartist': {0}",lang.tag_albumartist));
			Program.LogMessage(string.Format("Use '{0}' as bass dll path", dll_path));
			#endif

			//загружаем основную библитеку bass.dll и миксер bassmix.dll
			bool res=Bass.LoadMe(dll_path);
			if (!res)
			{
				BassException.ThrowOnError();
			}
			res = BassMix.LoadMe(dll_path);
			if (!res)
			{
				BassException.ThrowOnError();
			}

			#if DEBUG
			Program.LogMessage("Successfully load bass and bassmix");
			#endif

			try
			{
				//загружаем плагины

				#if DEBUG
				Program.LogMessage("Loading plugins...");
				#endif

				_loaded_plugins = Bass.BASS_PluginLoadDirectory(dll_path);

				#if DEBUG
				foreach (KeyValuePair<int, string> kvp in _loaded_plugins)
				{
					string p_text=string.Empty;
					BASS_PLUGININFO p_info=Bass.BASS_PluginGetInfo(kvp.Key);
					BASS_PLUGINFORM[] p_forms=p_info.formats;

					p_text = string.Format
						("{0} handle:0x{1:X} version:0x{2:X} formats:",
						 kvp.Value,
						 kvp.Key,
						 p_info.version);

					for (int i=0; i < p_info.formatc; i++)
					{
						p_text = p_text + p_forms[i].name + ", ";
					}
					Program.LogMessage(p_text);
				}
				#endif
				try
				{
					#if DEBUG
					Program.LogMessage("Init bass engine...");
					#endif
					BASSInit bass_init_flags=BASSInit.BASS_DEVICE_DEFAULT;
					if (Conf8Bit) bass_init_flags |= BASSInit.BASS_DEVICE_8BITS;
					if (ConfCalcLatency) bass_init_flags |= BASSInit.BASS_DEVICE_LATENCY;
					if (ConfMono) bass_init_flags |= BASSInit.BASS_DEVICE_MONO;

					if (!Bass.BASS_Init(ConfBassDevice, ConfBassSampleRate, bass_init_flags, Program.FormMain.Handle)) BassException.ThrowOnError();

					#if DEBUG
					Program.LogMessage("Bass inited.");
					BASS_INFO b_info=Bass.BASS_GetInfo();
					Program.LogMessage(string.Format("DirectSound version=0x{0:X}", b_info.dsver));
					Program.LogMessage(string.Format("Enable EAX={0}", b_info.eax));
					Program.LogMessage(string.Format("Caps={0}", b_info.flags.ToString()));
					Program.LogMessage(string.Format("Free 3D sample slots={0}", b_info.free3d));
					Program.LogMessage(string.Format("Free sample slots={0}", b_info.freesam));
					Program.LogMessage(string.Format("Output sample rate={0}", b_info.freq));
					Program.LogMessage(string.Format("Free device memory={0}", b_info.hwfree));
					Program.LogMessage(string.Format("Total device memory={0}", b_info.hwsize));
					Program.LogMessage(string.Format("Init flags={0}", b_info.initflags));
					Program.LogMessage(string.Format("Latency (milliseconds)={0}", b_info.latency));
					Program.LogMessage(string.Format("Maximum sample rate={0}", b_info.maxrate));
					Program.LogMessage(string.Format("Minimum sample rate={0}", b_info.minrate));
					Program.LogMessage(string.Format("Minimum recommended buffer length (milliseconds)={0}", b_info.minbuf));
					Program.LogMessage(string.Format("Number of available speakers={0}", b_info.speakers));
					Program.LogMessage("Start bass mixer now...");
					#endif

					BASSFlag mixer_flags=BASSFlag.BASS_MIXER_NONSTOP;
					if (Conf8Bit) mixer_flags |= BASSFlag.BASS_SAMPLE_8BITS;
					if (ConfSampleFloat) mixer_flags |= BASSFlag.BASS_SAMPLE_FLOAT;
					MixerHandle = Un4seen.Bass.AddOn.Mix.BassMix.BASS_Mixer_StreamCreate(ConfBassSampleRate, mixer_channels, mixer_flags);
					if (MixerHandle == 0) BassException.ThrowOnError();
					if (!Bass.BASS_ChannelPlay(MixerHandle, false)) BassException.ThrowOnError();
					#if DEBUG
					Program.LogMessage(string.Format("Mixer started. MixerHandle=0x{0:X}", MixerHandle));
					#endif
				}
				catch (Exception ex)
				{
					//critical exception
					MessageBox.Show(string.Format("Cannot start bass engine. {0}", ex.Message), ex.Source, MessageBoxButtons.OK, MessageBoxIcon.Error);
				}
			}
			catch (Exception ex)
			{
				//that is not critical
				#if DEBUG
				Program.LogMessage(ex.Message);
				#endif
			}
			
			//конф
			//_config = new BassConfig();
			//_config.PlaybackBufferLen = Program.options.PlaybackBufferLen;
			//_config.PlaybackUpdateBufferPeriod = Program.options.PlaybackUpdateBufferPeriod;
			//_config.StreamVolume = Program.options.GlobalStreamVolume;
			//_config.VolumeCurveLogarithmic = Program.options.LogarithmicCurve;
			//_config.PanCurveLogarithmic = Program.options.LogarithmicCurve;
			//_config.NetworkTimeout = Program.options.NetworkTimeout;
			//_config.NetworkBufferLen = Program.options.NetworkBuffer;
			//_config.NetworkPrebuffer = Program.options.NetworkPrebuffer;
			//_config.NetworkProxy = Program.options.NetworkProxy;
			////            _config.EnableUrlInPlaylist=Program.options.NetworkPlaylist;
			////            _config.RecordBufferLen=Program.options.RecordBuffer;
			//_config.UpdateThreadsNumber = Program.options.UpdateThreads;
			//_config.EnableTruepositionCaps = Program.options.TruePosition;
			//_config.AC3dynamicRange = Program.options.Ac3DynamicRange;
			////            _config.CDnumberOfRetries=Program.options.CdRetry;
			////            _config.CDreduceSpeedAfterError=Program.options.CdAutospeed;
			////            _config.CDskipError=Program.options.CdSkipError;
			//_config.CDfreeOld = false;
			//_config.ENCcastTimeout = Program.options.EncodeCastTimeout;
			//_config.MIDIautoLoadSoundfonts = Program.options.MidiAutosoundfont;
			//_config.MIDIdefaultSoundfont = Program.options.MidiDefaultSoundfont;
			////            _config.MIXorderOfAliasingFilter=Program.options.MixerFilterOrder;


			
			
			

			
			//        	string sme=Bass.SupportedMusicExtensions;
			//        	string sse=Bass.SupportedStreamExtensions;
			//        	string ssn=Bass.SupportedStreamName;

			//миксер:
			//миксер пока не используем. С миксером проблемно устанавливать точную синхронизацию
			//миксер может понадобится, если когда-нибудь добавим
			//возможность записывать все в файл или передавать на shoutcast
			//			BASSFlag mixer_flag=BASSFlag.BASS_STREAM_AUTOFREE;
			//			mixer_flag=Program.options.FloatSampleData? mixer_flag|BASSFlag.BASS_SAMPLE_FLOAT : mixer_flag;
			//			mixer_flag=Program.options.ForceSoftwareMixing? mixer_flag|BASSFlag.BASS_SAMPLE_SOFTWARE : mixer_flag;
			//			mixer_handle=BassMix.BASS_Mixer_StreamCreate
			//				(Program.options.OutputSampleRate,
			//				 Program.options.Mono? 1 : 2,
			//				 mixer_flag);
			//			Bass.BASS_ChannelPlay(mixer_handle,false);
		}
		#endregion

		#region Config

		public bool ConfMixerBuffer
		{
			get { return Properties.Settings.Default.MixerBuffer; }
			set { Properties.Settings.Default.MixerBuffer = value; }
		}

		public bool ConfDownloadReatrate
		{
			get { return Properties.Settings.Default.BassDownloadRestrate; }
			set { Properties.Settings.Default.BassDownloadRestrate = value; }
		}

		public bool ConfSampleFloat
		{
			get { return mxplay.Properties.Settings.Default.BassFloatSamples; }
			set { mxplay.Properties.Settings.Default.BassFloatSamples = value; }
		}

		[CategoryLocalized("BassEngineCateg",typeof(lang))]
		[DisplayNameLocalized("Bass8BitName",typeof(lang))]
		[DescriptionLocalized("Bass8BitDescription",typeof(lang))]
		[DefaultValue(false)]
		[TypeConverter(typeof(BooleanLocalizedConvertor))]
		public bool Conf8Bit
		{
			get { return mxplay.Properties.Settings.Default.Bass8Bit; }
			set { mxplay.Properties.Settings.Default.Bass8Bit = value; }
		}

		[CategoryLocalized("BassEngineCateg", typeof(lang))]
		[DisplayNameLocalized("BassMono",typeof(lang))]
		[DescriptionLocalized("BassMonoDescription",typeof(lang))]
		[DefaultValue(false)]
		[TypeConverter(typeof(BooleanLocalizedConvertor))]
		public bool ConfMono
		{
			get { return mxplay.Properties.Settings.Default.BassMono; }
			set { mxplay.Properties.Settings.Default.BassMono = value; }
		}

		[CategoryLocalized("BassEngineCateg", typeof(lang))]
		[DisplayNameLocalized("BassCalcLatency", typeof(lang))]
		[DescriptionLocalized("BassCalcLatencyDescription", typeof(lang))]
		[DefaultValue(false)]
		[TypeConverter(typeof(BooleanLocalizedConvertor))]
		public bool ConfCalcLatency
		{
			get { return mxplay.Properties.Settings.Default.BassCalcLatency; }
			set { mxplay.Properties.Settings.Default.BassCalcLatency = value; }
		}

		[CategoryLocalized("BassEngineCateg",typeof(lang))]
		[DisplayNameLocalized("BassDeviceName",typeof(lang))]
		[TypeConverter(typeof(BassDeviceConvertor))]
		public int ConfBassDevice
		{
			get
			{
				return mxplay.Properties.Settings.Default.BassDevice;
			}
			set
			{
				//после bass_init смена устройства на -1 или 0 не прокатывает,
				//поэтому только так: устройство изменится после рестарта
				mxplay.Properties.Settings.Default.BassDevice = value;
			}
		}

		[CategoryLocalized("BassEngineCateg", typeof(lang))]
		[DisplayNameLocalized("BassSamplerate", typeof(lang))]
		[DescriptionLocalized("BassSamplerateDescription",typeof(lang))]
		[DefaultValue(44100)]
		public int ConfBassSampleRate
		{
			get { return mxplay.Properties.Settings.Default.BassSampleRate; }
			set { mxplay.Properties.Settings.Default.BassSampleRate = value; }
		}

		#endregion

		internal int MixerHandle { get; private set; }
		internal int MixerChannels { get { return mixer_channels; } }

		private Dictionary<int, string> _loaded_plugins;
		//        private Dictionary<int, BassStreamCDA> _loaded_cda=new Dictionary<int, BassStreamCDA>();
		//        private int mixer_handle=0;
		
		//        public Dictionary<int, BassStreamCDA> LoadedCDA
		//        {
		//            get
		//            {
		//                return _loaded_cda;
		//            }
		//        }
		
		//        public string GetAllSupportedExtensions()
		//        {
		//            //from main dll:
		//            string ret=Bass.SupportedStreamExtensions;
		//            ret=ret+";"+Bass.SupportedMusicExtensions;
		
		//            //from plugins:
		//            foreach(KeyValuePair<int, string> entry in _loaded_plugins)
		//            {
		//                BASS_PLUGININFO p_info=Bass.BASS_PluginGetInfo(entry.Key);
		//                for (int i=0; i<p_info.formatc; i++)
		//                {
		//                    ret=ret+";"+p_info.formats[i].exts;
		//                }
		//            }
		
		//            return ret;
		//        }
		
		//        private BassConfig _config;
		//        public BassConfig Config
		//        {
		//            get
		//            {
		//                return _config;
		//            }
		//        }

		//        public int DeviceCount
		//        {
		//            get
		//            {
		//                int ret = Bass.BASS_GetDeviceCount();
		//                return ret;
		//            }
		//        }

		//        public BASS_DEVICEINFO[] Devices
		//        {
		//            get
		//            {
		//                Program.Messages.ShowMessage("Call BASS_GetDeviceinfos", MessageSeverity.Info);
		//                return Bass.BASS_GetDeviceInfos();
		//            }
		//        }

		//        public int MixerHandle
		//        {
		//            get
		//            {
		//                return mixer_handle;
		//            }
		//        }
		
		//        #region IDisposable Members

		~BassCore()
		{
			Dispose();
		}

		public void Dispose()
		{
			//_config.Dispose();

			bool success=false;
			//foreach (KeyValuePair<int, BassStreamCDA> kvp in LoadedCDA)
			//{
			//    kvp.Value.Dispose();
			//}

			foreach (KeyValuePair<int, string> kvp in _loaded_plugins)
			{
				success = Bass.BASS_PluginFree(kvp.Key);
			}

			//осбождаем миксер
			if (MixerHandle != 0)
			{
				Bass.BASS_ChannelStop(MixerHandle);
				Bass.BASS_StreamFree(MixerHandle);
				BassMix.FreeMe();
			}

			Bass.BASS_Stop();

			Bass.BASS_PluginFree(0); // то есть все плагины

			//if (mixer_handle != 0)
			//{
			//    Program.Messages.ShowMessage("Stop mixer now...", MessageSeverity.Info);
			//    Bass.BASS_ChannelStop(mixer_handle);
			//    Program.Messages.ShowMessage("And unload bassmix.dll...", MessageSeverity.Info);
			//    success = BassMix.FreeMe();
			//    Program.Messages.ShowMessage(success ? "Success" : "Fail", MessageSeverity.Info);
			//}

			success = Bass.BASS_Free();
			success = Bass.FreeMe();
			GC.SuppressFinalize(this);
		}

		//        #endregion
	}
}
